package com.example.spider.config.support;

import com.example.spider.config.support.method.InvocableSpringBeanMethod;
import com.example.spider.config.support.method.MethodArgumentResolver;
import com.example.spider.config.support.method.MethodArgumentResolverComposite;
import com.example.spider.task.SpiderResultHandler;
import com.example.spider.task.Task;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jakarta.annotation.Nullable;

/**
 * import 该类
 * afterPropertiesSet
 * 参考了 ServletInvocableHandlerMethod，制定exHandlerInvocableHandlerMethod
 *
 * @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
 */
public class ExceptionHandlerExceptionResolver extends AbstractSpiderExceptionResolver
        implements ApplicationContextAware, InitializingBean {

    /**
     * P1
     */
    @Nullable
    private List<MethodArgumentResolver> customArgumentResolvers;

    /**
     * P1
     */
    @Nullable
    private MethodArgumentResolverComposite argumentResolvers;

    // 暂时不需要返回值处理器 / 返回内容协商，因为 spider 只有是否重试/丢弃任务/延时重试，mock爬虫响应不太会有。
    // handler 无返回值也不需要

    //private final List<Object> responseBodyAdvice = new ArrayList<>();

    @Nullable
    private ApplicationContext applicationContext;

    /**
     * ex -> exHandler 缓存
     */
    private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);

    /**
     * AdviceBean 缓存
     */
    private final Map<SpiderHandlerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

    /**
     * Provide resolvers for custom argument types. Custom resolvers are ordered
     * after built-in ones. To override the built-in support for argument
     * resolution use {@link #setArgumentResolvers} instead.
     */
    public void setCustomArgumentResolvers(@Nullable List<MethodArgumentResolver> argumentResolvers) {
        this.customArgumentResolvers = argumentResolvers;
    }

    /**
     * Return the custom argument resolvers, or {@code null}.
     */
    @Nullable
    public List<MethodArgumentResolver> getCustomArgumentResolvers() {
        return this.customArgumentResolvers;
    }

    /**
     * Configure the complete list of supported argument types thus overriding
     * the resolvers that would otherwise be configured by default.
     */
    public void setArgumentResolvers(@Nullable List<MethodArgumentResolver> argumentResolvers) {
        if (argumentResolvers == null) {
            this.argumentResolvers = null;
        } else {
            this.argumentResolvers = new MethodArgumentResolverComposite();
            this.argumentResolvers.addResolvers(argumentResolvers);
        }
    }

    /**
     * Return the configured argument resolvers, or possibly {@code null} if
     * not initialized yet via {@link #afterPropertiesSet()}.
     */
    @Nullable
    public MethodArgumentResolverComposite getArgumentResolvers() {
        return this.argumentResolvers;
    }

    @Nullable
    public ApplicationContext getApplicationContext() {
        return this.applicationContext;
    }

    @Override
    public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans 主要功能
        initExceptionHandlerAdviceCache();

        if (this.argumentResolvers == null) {
            List<MethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new MethodArgumentResolverComposite().addResolvers(resolvers);
        }
    }

    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }

        List<SpiderHandlerAdviceBean> adviceBeans = SpiderHandlerAdviceBean.findAnnotatedBeans(getApplicationContext());
        for (SpiderHandlerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for SpiderHandlerAdviceBean: " + adviceBean);
            }
            for (ExceptionAdvanceEnum advanceEnum : ExceptionAdvanceEnum.values()) {
                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType, advanceEnum);
                if (resolver.hasExceptionMappings()) {
                    this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                }
            }
            //if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            //    this.responseBodyAdvice.add(adviceBean);
            //}
        }

        if (logger.isDebugEnabled()) {
            int handlerSize = this.exceptionHandlerAdviceCache.size();
            //int adviceSize = this.responseBodyAdvice.size();
            if (handlerSize == 0) {
                logger.debug("SpiderHandlerAdvice beans: none");
            } else {
                logger.debug("SpiderHandlerAdvice beans: " + handlerSize + " @ExceptionHandler.");
            }
        }
    }

    public Map<SpiderHandlerAdviceBean, ExceptionHandlerMethodResolver> getExceptionHandlerAdviceCache() {
        return Collections.unmodifiableMap(this.exceptionHandlerAdviceCache);
    }

    /**
     * Return the list of argument resolvers to use including built-in resolvers
     * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
     */
    protected List<MethodArgumentResolver> getDefaultArgumentResolvers() {
        List<MethodArgumentResolver> resolvers = new ArrayList<>();

        // Annotation-based argument resolution 暂时不需要
        // resolvers.add(new SessionAttributeMethodArgumentResolver());
        // resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        //resolvers.add(new ServletRequestMethodArgumentResolver());
        //resolvers.add(new ServletResponseMethodArgumentResolver());
        //resolvers.add(new RedirectAttributesMethodArgumentResolver());
        //resolvers.add(new ModelMethodProcessor());

        resolvers.add(new SpiderProxyInfoArgumentResolver());
        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // Catch-all 暂时不需要
        // resolvers.add(new PrincipalMethodArgumentResolver());

        return resolvers;
    }

    @Override
    protected boolean hasGlobalExceptionHandlers() {
        return !this.exceptionHandlerAdviceCache.isEmpty();
    }

    /**
     * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
     */
    @Override
    @Nullable
    protected <T> Object doResolveException(Task<T> task, T spiderResult, @Nullable Object handler,
                                            Exception exception) {

        InvocableSpringBeanMethod exceptionHandlerMethod = getExceptionHandlerMethod(handler, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }

        if (this.argumentResolvers != null) {
            exceptionHandlerMethod.setMethodArgumentResolvers(this.argumentResolvers);
        }

        //ServletWebRequest webRequest = new ServletWebRequest(task, spiderResult);
        //ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        Object[] params = new Object[3];
        params[0] = task;
        params[1] = spiderResult;
        params[2] = exception;

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
            }
            // todo 调整参数，按照参数调用方法
            exceptionHandlerMethod.invokeForRequest(params);
        } catch (Throwable invocationEx) {
            logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
            // Continue with default processing of the original exception...
            return null;
        }

        return null;
    }


    @Nullable
    protected InvocableSpringBeanMethod getExceptionHandlerMethod(@Nullable Object handler, Exception exception) {

        Class<?> handlerType = null;

        if (handler != null) {
            // Local exception handler methods on the controller class itself.
            // To be invoked through the proxy, even in case of an interface-based proxy.
            handlerType = handler.getClass();
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                // 根据handler类型判断是 TaskResultHandler 还是 Spider
                ExceptionAdvanceEnum exceptionAdvanceEnum = SpiderResultHandler.class.isAssignableFrom(handlerType)
                        ? ExceptionAdvanceEnum.HANDLER_ANNO : ExceptionAdvanceEnum.SPIDER_ANNO;
                resolver = new ExceptionHandlerMethodResolver(handlerType, exceptionAdvanceEnum);
                this.exceptionHandlerCache.put(handlerType, resolver);
            }
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new InvocableSpringBeanMethod(handler, method, this.applicationContext);
            }
            // For advice applicability check below (involving base packages, assignable types
            // and annotation presence), use target class instead of interface-based proxy.
            if (Proxy.isProxyClass(handlerType)) {
                handlerType = AopUtils.getTargetClass(handler);
            }
        }

        // 全局异常解析
        for (Map.Entry<SpiderHandlerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            SpiderHandlerAdviceBean advice = entry.getKey();
            // Advice是否支持该Handler，包路径、继承关系、特殊注解标记
            if (advice.isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new InvocableSpringBeanMethod(advice.resolveBean(), method, this.applicationContext);
                }
            }
        }

        return null;
    }

}
